다대일(many to one) 구조의 다층 RNN: IMDb 영화 리뷰의 감성 분석다대다(many to many)구조의 다층 RNN: 글자단위 언어 모델링
IMDb
DataSet
import tensorflow as tf
import tensorflow_datasets as tfds
import numpy as np
import pandas as pd
df=pd.read_csv('/Users/csian/Desktop/CP/data_set/movie_data.csv', encoding='utf-8')
Preprocess
1. 텐서플로 데이터셋 객체를 만들고 훈련, 테스트, 검증 데이터셋으로 분할
2. 훈련 데이터셋에 있는 고유한 단어를 찾는다.
3. 고유한 단어를 고유한 정수로 매핑하고 리뷰 텍스트를 정수(고유 단어의 인덱스) 배열로 인코딩
4. 모델에 입력하기 위해 데이터셋을 미니 배치로 나눈다.
target=df.pop('sentiment')
ds_raw=tf.data.Dataset.from_tensor_slices((df.values, target.values))
for ex in ds_raw.take(3):
tf.print(ex[0].numpy()[0][:50], ex[1])
b'at a Saturday matinee in my home town. I went with' 0
b'I love this movie. It is the first film Master P h' 1
b'In the voice over which begins the film, Hughie(Bi' 1
tf.random.set_seed(1)
ds_raw=ds_raw.shuffle(50000, reshuffle_each_iteration=False)
ds_raw_test=ds_raw.take(25000)
ds_raw_train_valid=ds_raw.skip(25000)
ds_raw_train=ds_raw_train_valid.take(20000)
ds_raw_valid=ds_raw_train_valid.skip(20000)
test for 25,000
valid for 5,000
train for 20,000
훈련 데이터셋에서 고유한 단어(토큰)을 찾아야 한다.
파이썬 표준 라이브러리 collections.Counter 클래스를 사용
아래에서는 Counter 객체(token_counts)를 만들어 고유한 단어의 빈도를 수집한다.
BoW와 달리 고유 단어 집합에만 관심이 있고, 부수적으로 만들어진 단어 카운트는 필요하지 않다.
텍스트를 단어(또는 토큰)으로 나누려면 tensorflow_datasets 패키지가 제공하는 Tokenizer 클래스를 사용
from collections import Counter
tokenizer=tfds.deprecated.text.Tokenizer()
token_counts=Counter()
for example in ds_raw_train:
tokens=tokenizer.tokenize(example[0].numpy()[0])
token_counts.update(tokens)
print('어휘 사전 크기:', len(token_counts))
token_counts 단어는 키, 키에 매핑된 값은 고유한 정수이다.
tensorflow_datasets 패키지는 이런 매핑과 전체 데이터셋을 인코딩할 수 있는 TokenTextEncoder클래스를 제공한다.
(token_counts에 고유 단어의 횟수는 사용하지 않음)
encoder=tfds.deprecated.text.TokenTextEncoder(token_counts)
example_str="This is an example!"
print(encoder.encode(example_str))
훈련데이터에서 매핑되지 않은 새로운 토큰이 검증 데이터와 테스트 데이터에서 나오면,
모든 정수는 q+1에 할당된다.(q는 TokenTextEncoder에 전달된 token_counts의 크기: 86963)
텍스트 데이터가 텐서 객체에 들어 있어 즉시 실행 모드에서 텐서의 numpy() 메서드로 참조할 수 있다.하지만 map() 메서드로 변환하는 동안에는 즉시 실행이 비활성화 된다.
위의 문제를 해결하기 위해서 두 개의 함수를 정의한다.
tf.py_function()
def encode(text_tensor, label):
text=text_tensor.numpy()[0]
encoded_text=encoder.encode(text)
return encoded_text, label
def encode_map_fn(text, label):
return tf.py_function(encode, inp=[text, label], Tout=(tf.int64, tf.int64))
ds_train=ds_raw_train.map(encode_map_fn)
ds_valid=ds_raw_valid.map(encode_map_fn)
ds_test=ds_raw_test.map(encode_map_fn)
tf.random.set_seed(1)
for example in ds_train.shuffle(1000).take(5):
print('시퀀스 길이:', example[0].shape)
시퀀스 길이: (137,)
시퀀스 길이: (225,)
시퀀스 길이: (625,)
시퀀스 길이: (177,)
시퀀스 길이: (527,)
샘플들의 단어 시퀀스 길이가 다르다.
일반적으로 RNN은 다른 시퀀스를 다룰 수 있으나,
미니 배치에 있는 시퀀스는 효율적으로 텐서에 저장하기 위해 동일한 길이가 되어야 한다.
padded_batch()
크기가 다른 원소를 가진 데이터셋을 미니 배치로 나누기 위해 텐서플로의 batch() 대신 padded_batch() 메서드를 사용할 수 있다.
하나의 배치에 포함되는 모든 원소를 자동으로 0으로 패딩하여 배치에 있는 모든 시퀀스가 동일한 길이가 되도록 만든다.
ds_subset=ds_train.take(8)
for example in ds_subset:
print('개별 샘플 크기:', example[0].shape)
ds_batched=ds_subset.padded_batch(4, padded_shapes=([-1], []))
for batch in ds_batched:
print('배치 차원:', batch[0].shape)
개별 샘플 크기: (152,)
개별 샘플 크기: (77,)
개별 샘플 크기: (178,)
개별 샘플 크기: (130,)
개별 샘플 크기: (172,)
개별 샘플 크기: (164,)
개별 샘플 크기: (100,)
개별 샘플 크기: (112,)
배치 차원: (4, 178)
배치 차원: (4, 172)
처음 네개의 샘플이 하나의 배치가 되었고, 이 샘플 중에 가장 큰 크기를 사용했다.
(나머지 3개의 샘플들은 배치의 최대 크기에 맞도록 필요한 만큼 패딩을 추가)
train_data=ds_train.padded_batch(32, padded_shapes=([-1], []))
valid_data=ds_valid.padded_batch(32, padded_shapes=([-1], []))
test_data=ds_test.padded_batch(32, padded_shapes=([-1], []))